#ifndef __CNCTASK_H__
#define __CNCTASK_H__

#include "tbb/combinable.h"
#include "cnc/cnc.h"
#include "cnc/default_tuner.h"

using namespace std;

#include "cnc/debug.h"

// forward declaration
class CNCTask_context;

#include "../common/TaskTag.hpp"
#include "../support/Threading.hpp"
#include "../support/Timing.hpp"

// CNCTask core class
template <int TASK_ID, int TASKTAGDIM, int INTERTASKDIM> class CNCTask_step {
public:
  // TLS using GCC / CLANG syntax
  static __thread bool initialized;
  CNCTask_step() {}

  int64_t execute
  (const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &, CNCTask_context &) const;
};

// CNCTask tuner class
template <int TASK_ID, int TASKTAGDIM, int INTERTASKDIM> class CNCTask_tuner : 
  public CnC::step_tuner<>, public CnC::tag_tuner<>, public CnC::hashmap_tuner {
public:
  // TLS using TBB
  mutable tbb::combinable<bool> initialized; // make private someday

  CNCTask_tuner() : initialized(false) {}
  template<class DC>
  void depends(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> &, 
               CNCTask_context &, DC &) const;
};

template< int TASK_ID, int TASKTAGDIM, int INTERTASKDIM >
struct cnc_tag_hash_compare< TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM> >
{
  size_t hash(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>& tt) const
  {
    return  tt.hashCode();
  }

  bool equal(const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>& a, 
             const TaskTag<TASK_ID, TASKTAGDIM, INTERTASKDIM>& b) const
  {
    return a.equals(b);
  }
};

// Context for a single CNCTask 
class CNCTask_context :
public CnC::context< CNCTask_context> {
public:
  void *wA;

  CNCTask_tuner <0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> m_tuner;
  CnC::item_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        void *, CNCTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> > items;
  CnC::tag_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                       CNCTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>
                       > m_tags;
  CnC::step_collection< CNCTask_step<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                        CNCTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>
                        > m_steps;

  inline CnC::item_collection< TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO>,
                               void *, CNCTask_tuner<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> >&
  getItemCollection(const TaskTag<0, TASKTAGDIMMACRO, INTERTASKDIMMACRO> &) {
    return items;
  }

  CNCTask_context(void *ptr) :
    wA(ptr), m_tuner(), items(*this, "items", m_tuner), m_tags(*this), m_steps(*this)
  {
    Threading::dumpThreadInfo(std::cerr, " cnctask_init ");

    // prescription
    m_tags.prescribes( m_steps, *this );

    // producer/consumer
    m_steps.consumes( items );
    m_steps.produces( items );
  }
};


#include "../common/Runnable.hpp"

template <int TASK_ID, int NPARAMS, int TASKTAGDIM, int INTERTASKDIM>
class CNCTask : public Runnable {
  int id = TASK_ID;
  void *arrayPtr;
  std::array<int64_t, NPARAMS> params;

public:
  template <typename... Sizes> 
  CNCTask(void *a, Sizes... sizes) : arrayPtr(a), params{{std::forward<Sizes>(sizes)...}} {
    static_assert(NPARAMS == sizeof...(Sizes), "ERROR");
    assert(arrayPtr != nullptr);
  }

  void operator()();
  
protected:
  // Virtual 
  void print(ostream &os) {
    os << "CnC version";
  }
};


template <int TASK_ID, int TASKTAGDIM, int INTERTASKDIM>
__thread bool CNCTask_step<TASK_ID, TASKTAGDIM, INTERTASKDIM>::initialized = false;
#endif
